package com.weidey.blogWeb.service.Impl;

import com.weidey.blogWeb.mapper.WebLIkeMapper;
import com.weidey.blogWeb.service.ILikeService;
import com.weidey.common.core.domain.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

@Service
public class LikeServiceImpl  implements ILikeService {

    // 注入redis RedisTemplate
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Qualifier("threadPoolTaskExecutor")
    @Autowired
    private AsyncTaskExecutor asyncTaskExecutor;

    @Autowired
    private WebLIkeMapper webLIkeMapper;

    /**
     * 点赞,收藏,关注业务
     * @param userId  用户id
     * @param targetType 类型
     * @param targetId 目标id
     * @return 点赞 and 取消点赞
     */
    @Override
    public AjaxResult like(Long userId, Integer targetType, Long targetId) {
        String redisKey = String.format("user:like:%d:%d:%d",userId ,targetType, targetId);
        String counterKey = String.format("count:%d:%d", targetType, targetId);

        // 使用Lua脚本保证原子性操作（解决并发问题）
        String luaScript =
                "local key = KEYS[1]\n" +
                        "local counterKey = KEYS[2]\n" +
                        "local exists = redis.call('EXISTS', key)\n" +
                        "if exists == 1 then\n" +
                        "    redis.call('DEL', key)\n" +
                        "    redis.call('HINCRBY', counterKey, 'like', -1)\n" +
                        "    return 0\n" + // 返回取消点赞
                        "else\n" +
                        "    redis.call('SET', key, 1, 'EX', 2592000)\n" + // 30天过期
                        "    redis.call('HINCRBY', counterKey, 'like', 1)\n" +
                        "    return 1\n" + // 返回点赞成功
                        "end";

        RedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
        Long result = redisTemplate.execute(script, Arrays.asList(redisKey, counterKey));

        // 异步持久化
        asyncTaskExecutor.execute(() -> {
            int operation = result == 1 ? 1 : -1;
            // 批量写入数据库
            writeToDatabase(userId, targetType, targetId, operation);
        });

        String  ResultType = convertResultType(targetType);

        return result == 1 ?
                AjaxResult.success(ResultType+"成功") :
                AjaxResult.warn("取消"+ResultType);
    }

    @Override
    public Boolean isLike(Long userId, Integer targetType, Long targetId) {
        String redisKey = String.format("user:like:%d:%d:%d", userId, targetType, targetId);

        // 使用Redis原子GETSET命令确保强一致性
        return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
            byte[] key = redisKey.getBytes();
            Boolean exists = connection.exists(key);
            if (exists != null && exists) {
                return true;
            }

            // 查数据库时添加分布式锁
            String lockKey = "lock:" + redisKey;
            boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 3, TimeUnit.SECONDS);
            if (lock) {
                try {
                    Integer status = webLIkeMapper.selectLike(userId, targetType, targetId);
                    if (status != null && status == 1) {
                        connection.setEx(key, 2592000, "1".getBytes()); // 30天
                        return true;
                    }
                } finally {
                    redisTemplate.delete(lockKey);
                }
            }
            return false;
        });
    }

    @Override
    public Integer getCount(Integer targetType, Long targetId) {
        // 优先从Redis获取实时计数
        String counterKey = String.format("count:%d:%d", targetType, targetId);
        Object countObj = redisTemplate.opsForHash().get(counterKey, "like");

        if (countObj != null) {
            return  Integer.parseInt(countObj.toString());
        }
        // 从数据库兜底查询
        Integer dbCount = webLIkeMapper.selectLikeCount(targetType,targetId);

        // 回写Redis防止缓存穿透
        if (dbCount != null) {
            redisTemplate.opsForHash().put(counterKey, "like", dbCount.toString());
            redisTemplate.expire(counterKey, 5, TimeUnit.MINUTES); // 短期缓存
            return dbCount;
        }
        return 0;
    }

    private String convertResultType(int redisField) {
        switch (redisField) {
            case 1: return "点赞";
            case 2: return "收藏";
            case 3: return "关注";
            case 4: return "点赞";  //评论点赞
            default: throw new IllegalArgumentException("未知的计数类型");
        }
    }

    @Async
    protected void writeToDatabase(Long userId, Integer targetType, Long targetId, int operation) {
        try {
            // 使用CAS乐观锁更新
            int rows = webLIkeMapper.updateLikeStatus(
                    userId,
                    targetType,
                    targetId,
                    operation > 0 ? 1 : 0
            );

            // 处理首次插入场景
            if (rows == 0) {
                webLIkeMapper.insertLike(
                        userId,
                        targetType,
                        targetId,
                        operation > 0 ? 1 : 0
                );
            }
            // 计数器更新增加边界检查
            webLIkeMapper.updateCount(
                    targetType,
                    targetId,
                    Math.max(operation, 0), // 防止负数累计
                    "like"
            );
        } catch (DuplicateKeyException e) {
            // 处理并发插入冲突
            webLIkeMapper.updateLikeStatus(
                    userId,
                    targetType,
                    targetId,
                    operation > 0 ? 1 : 0
            );
        }
    }
}
